home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Magnum One
/
Magnum One (Mid-American Digital) (Disc Manufacturing).iso
/
d12
/
ddj0190.arc
/
NOLAN.LST
< prev
next >
Wrap
File List
|
1989-12-19
|
15KB
|
378 lines
_REAL-TIME DATA ACQUISITION USING DMA_
by Tom Nolan
[LISTING ONE]
/*--------------------------------------------------------------------------*/
/* dma.c -- subroutines for dma data acquisition
* The calling routine must declare the variables in the "extern" list
* below, and the reset_irq() function. Communication from the main
* program to the subroutines is mostly through these global variables.
* The calling routine must give values to dma_chan, dma_irq and buf_size,
* then call alloc_dma_buf(), dma_setup(), and start_dma(). As each
* dma buffer fills up, the interrupt service routine calls start_dma() on
* the next buffer. The calling routine can wait for buf_index to
* change, then process data pointed to by curr_buf. Cleanup is done
* by dma_finish(), which is called automatically when the program exits.
* Compiler: Microsoft C Version 5.0
* Set /Gs switch to remove stack probes (a necessity for any
* function called at interrupt state!)
* Tom Nolan - 8/7/89
*/
#include <dos.h>
/* DMA Register Definitions */
#define DMA0_BASE 0x00 /* address of dma controller (chan 0-3) */
#define DMA1_BASE 0xC0 /* address of dma controller (chan 4-7) */
/* Interrupt Controller Definitions */
#define INTA00 0x20 /* base address of int ctrlr */
#define INTA01 0x21 /* address of int ctrlr 2nd reg */
#define EOI 0x20 /* code for non-specific end-of-int */
/* External Variables */
extern char far *dma_buffers[]; /* array containing buffer addresses */
extern int buf_index; /* index of current buffer in array */
extern char far *curr_buf; /* pointer to just-filled buffer */
extern unsigned buf_size; /* size of buffers in bytes */
extern int lost_buffers; /* count of buffers unable to be written */
extern int dma_irq; /* h/w interrupt when dma complete (0-7) */
extern int dma_chan; /* channel number for dma operation */
extern int file_handle; /* handle of archive file (0=no file) */
extern void reset_irq(); /* function to reset interrupt request */
/* local variables - placed in static storage to
* avoid excessive stack usage in interrupt routines */
static union REGS r; /* general registers */
static struct SREGS s; /* segment registers */
static int sel; /* dma channel select bits */
static int basereg; /* dma controller base address register */
static int cntreg; /* dma controller count register */
static int maskreg; /* dma controller mask register */
static int modereg; /* dma controller mode register */
static int pagereg; /* dma page address register */
static int page_tbl[] = /* table of page register addresses */
{ 0x87, 0x83, 0x81, 0x82, /* for dma channels 0, 1, 2, 3 */
0x8f, 0x8b, 0x89, 0x8a }; /* 4, 5, 6, 7 */
char far *dos_crit_addr; /* address of DOS critical section flag */
static void /* space for saved int vector contents */
(interrupt far *dma_int_save)();
/* macros for extracting bytes from 20-bit addresses */
#define LSB(x) *((unsigned char *) &x)
#define MSB(x) *(((unsigned char *) &x) + 1)
#define PAGE(x) *(((unsigned char *) &x) + 2)
/* Function Prototypes */
void dma_setup(void);
void dma_finish(void);
int alloc_dma_buf(void);
void start_dma(char far *, unsigned);
void interrupt far dma_isr(void);
int write_buf();
/*--------------------------------------------------------------------------*/
int alloc_dma_buf() /* allocate a pair of dma buffers */
{
unsigned buf; /* temp variables for various */
unsigned max; /* paragraph addreses */
unsigned seg;
unsigned size; /* buffer size in paragraphs */
/* This routine allocates a pair of buffers that can be
* filled by dma. The buffers are guaranteed to be
* aligned so that they do not cross physical page boundaries.
* Before calling this routine, set the value of buf_size to
* the required number of bytes in each buffer. The maximum
* buffer size is 64K bytes, which can be allocated
* by specifying a buf_size of zero. The byte count is converted
* to paragraphs, which are the units the DOS memory allocation
* functions work with. Buffer addresses returned in dma_buffers[0]
* and dma_buffers[1]. Return value is zero if allocation succeeded,
* non-zero (an MS-DOS error code) otherwise.
*/
size = (buf_size == 0) ? /* convert bytes to paragraphs */
0x1000 : buf_size >> 4; /* ..by dividing by 16 */
_dos_allocmem(0xffff, &max); /* get max paragraphs from dos */
_dos_allocmem(max, &seg); /* now grab it all */
buf = seg; /* initial attempt at buffer segment */
if( ((buf + size - 1) & 0xf000) /* if buffer crosses */
!= (buf & 0xf000) ) /* phys page bdry */
buf = (buf & 0xf000) + 0x1000; /*...adjust to next phys page */
dma_buffers[0] = (char far *) /* convert buffer segment */
((long) buf << 16); /*... to far pointer for return */
buf += size; /* initial attempt at next buffer */
if( ((buf + size - 1) & 0xf000)
!= (buf & 0xf000) )
buf = (buf & 0xf000) + 0x1000; /* adjust if crosses page bdry */
dma_buffers[1] = (char far *) /* return it as a far pointer */
((long) buf << 16);
size = buf + size - seg; /* compute actual size needed */
return /* free unneeded memory and */
_dos_setblock(size, seg, &max); /* return error if not enough */
}
/*--------------------------------------------------------------------------*/
void dma_setup() /* set up for dma operations */
{
/* Before calling this routine set the following variables:
* dma_chan - channel number (hardware dependent)
* dma_irq - interrupt request number 0-7 (hardware dependent)
*/
int intmsk;
sel = dma_chan & 3; /* isolate channel select bits */
pagereg = page_tbl[dma_chan]; /* locate corresponding page reg */
if(dma_chan < 4) /* setup depends on chan number */
{
basereg = DMA0_BASE + sel * 2; /* standard dma controller */
cntreg = basereg + 1; /* note that this controller */
maskreg = DMA0_BASE + 10; /* is addressed on byte */
modereg = DMA0_BASE + 11; /* boundaries */
}
else
{
basereg = DMA1_BASE + sel * 4; /* alternate dma ctrlr (AT only) */
cntreg = basereg + 2; /* note that this controller */
maskreg = DMA1_BASE + 20; /* is addressed on word */
modereg = DMA1_BASE + 22; /* boundaries */
}
r.h.ah = 0x34; /* dos "get critical flag addr" function */
intdosx(&r, &r, &s);
dos_crit_addr = (char far *) /* save its address so it can be tested */
(((long) s.es << 16) | r.x.bx); /* ... as a far pointer */
if(dma_irq < 0 || dma_irq > 7) /* validate interrupt number */
return;
dma_int_save = /* save current contents of dma int vec */
_dos_getvect(dma_irq + 8);
_dos_setvect(dma_irq+8, dma_isr); /* set up new int service routine */
intmsk = inp(INTA01); /* get current interrupt enable mask */
intmsk &= ~(1 << dma_irq); /* clear mask bit for dma interrupt */
outp(INTA01, intmsk); /* output new mask, enabling interrupt */
atexit(dma_finish); /* register exit function */
}
/*--------------------------------------------------------------------------*/
static void dma_finish() /* called via atexit() mechanism */
{
int intmsk;
intmsk = inp(INTA01); /* get current interrupt enable mask */
intmsk |= (1 << dma_irq); /* set mask bit for dma interrupt */
outp(INTA01, intmsk); /* output new mask, disabling interrupt */
_dos_setvect(dma_irq+8, dma_int_save);/* restore old vector contents */
}
/*--------------------------------------------------------------------------*/
void start_dma(buf, count) /* start a dma operation */
char far *buf; /* address of buffer to be filled */
unsigned count; /* size of buffer in bytes */
{
int page;
unsigned long addr = /* 20-bit address of dma buffer */
FP_OFF(buf) +
(long) FP_SEG(buf) << 4;
/* This routine starts a dma operation. It needs to know:
* - the address where the dma buffer starts;
* - the number of bytes to transfer;
* The dma buffer address is supplied in segmented, far-pointer
* form (as returned by alloc_dma_buf()). In this routine it is
* converted to a 20-bit address by combining the segment and
* offset. The upper four bits are known as the page number, and
* are handled separately from the lower 16 bits. The transfer
* count is decremented by 1 because the dma controller reaches
* terminal count when the count rolls over from 0000 to ffff.
*
* The dma transfer stops when the channel reaches terminal count.
* The terminal count signal is turned around in the interface
* hardware to produce an interrupt when dma is complete.
*
* Channels 4-7 are on a separate dma controller, available on
* the PC-AT only. They perform 16-bit transfers instead of 8-bit
* transfers, and they are addressed in words instead of bytes.
* This routine handles the addressing requirements based
* on the channel number.
*
* dma_setup() needs to be called before start_dma() in order to
* assign values to maskreg, modereg, etc.
*/
page = PAGE(addr); /* extract upper bits of address */
if(dma_chan >= 4) /* for word-oriented channels... */
{
count >>= 1; /* convert count to words */
addr >>= 1; /* convert address to words */
page &= 0x7e; /* address bit 16 is now in 'addr' */
}
count--; /* compute count-1 (xfr stops at ffff) */
outp(maskreg, sel | 0x04); /* set mask bit to disable dma */
outp(modereg, sel | 0x44);/* xfr mode (sngl, inc, noinit, write) */
outp(basereg, LSB(addr) ); /* output base address lsb */
outp(basereg, MSB(addr) ); /* output base address msb */
outp(pagereg, page );/* output page number to page register */
outp(cntreg, LSB(count)); /* output count lsb */
outp(cntreg, MSB(count)); /* output count msb */
outp(maskreg, sel ); /* clear mask bit, enabling dma */
}
/*--------------------------------------------------------------------------*/
static void interrupt far dma_isr()
{
/* This routine is entered upon completion of a dma operation.
* At this point the current dma buffer is full and we can
* write it to disk. We set the "available data" pointer
* to point to the just-filled buffer, and start the next dma
* operation on the other buffer. At the conclusion of
* operations, we output a non-specific end-of-interrupt
* to the interrupt controller.
*
* The PC bus provides no mechanism for "unlatching" an
* interrupt request once it has been serviced. In order to
* enable the next interrupt, the hardware must be designed
* so that the request can be reset, by a write to an i/o
* port, for example. The external routine reset_irq()
* must be coded to perform this function.
*
* Declaring this routine as type 'interrupt', ensures
* that all registers are saved, the C data segment is set
* correctly, and that the routine returns with an IRET
* instruction. Further interrupts are disabled during the
* execution of this routine.
*/
curr_buf = dma_buffers[buf_index]; /* post just-filled buf address */
buf_index ^= 1; /* index next buffer */
start_dma(dma_buffers[buf_index], /* start dma on next buffer */
buf_size);
if( file_handle ) /* if disk is enabled.. */
write_buf(); /* write buffer to disk */
reset_irq(); /* do hardware-specific reset */
outp(INTA00, EOI); /* signal end of int */
}
/*--------------------------------------------------------------------------*/
static int write_buf() /* write buffer to disk file */
{
if( *dos_crit_addr ) /* first check dos critical section flag */
{
lost_buffers++; /* ..if set, skip writing this buffer */
return 0; /* ..not really an error in this case */
}
r.x.dx = FP_OFF(curr_buf); /* ok to write now, set address in */
s.ds = FP_SEG(curr_buf); /* proper registers for dos call */
r.x.bx = file_handle; /* set file handle to write to */
r.x.cx = buf_size; /* set byte count for write */
/* WARNING - can't write 64K! */
r.h.ah = 0x40; /* dos write-to-file-handle function */
if(intdosx(&r, &r, &s) == buf_size /* check return value and.. */
&& r.x.cflag == 0) /* ..carry flag for success code */
return 0; /* return success */
else
{
lost_buffers++; /* didn't write this buffer */
return 1; /* return failure */
}
}
[LISTING TWO]
/*--------------------------------------------------------------------------*/
/* test.c -- test dma data acquisition
* Compiler: Microsoft C Version 5.0
* Must compile with -Gs option because
* reset_irq() is called from interrupt.
* Tom Nolan - 8/7/89
*/
#include <bios.h>
int far *dma_buffers[2]; /* pointers to two buffers */
int far *curr_buf; /* pointer to current buffer */
int buf_size; /* buffer size */
int buf_index; /* index of current buffer */
int dma_irq = 3; /* hardware int request line */
int dma_chan = 1; /* hardware dma channel number */
int file_handle = 0; /* file handle */
int lost_buffers = 0; /* write errors */
/* In this program, each dma buffer will be filled with
* NUMFR "frames", each of size FRSIZE (in words). The second
* word of each frame is a frame counter, which increments
* modulo 256. The program checks the frame counters to make
* sure they are sequential and no data was lost.
*/
#define FRSIZE 64 /* words per frame */
#define NUMFR 8 /* frames per dma buffer */
/*--------------------------------------------------------------------------*/
main()
{
int temp;
int i;
unsigned char frame;
int far *cp;
reset_irq(); /* clear interrupt request */
buf_size = FRSIZE * NUMFR * sizeof(int); /* figure out buffer size */
alloc_dma_buf(); /* allocate buffers */
printf("buf1 = %p buf2 = %p\n", /* informational output */
dma_buffers[0], dma_buffers[1]);
dma_setup(); /* set up for dma operations */
outp(0x3a0,0); /* reset fifo on hw interface */
start_dma(dma_buffers[0], buf_size); /* start up the data acq */
file_handle = creat("tmp.dat"); /* open a file for raw data */
temp = buf_index;
while( !_bios_keybrd(_KEYBRD_READY) ) /* quit on next keystroke */
{
if(temp != buf_index) /* wait for dma complete */
{
printf("%d: ",temp); /* print buffer index */
for(i=0, cp = curr_buf+1; i<NUMFR; i++, cp += FRSIZE)
{
if(frame != *cp) printf("*"); /* frame counter bad */
else printf(" "); /* frame counter good */
printf("%04x", *cp); /* print frame counter */
frame = *cp + 1;/* next expected counter value */
}
printf(" : %d\n",lost_buffers); /* keep track of lost writes */
temp = buf_index; /* next expected buffer number */
}
}
close(file_handle); /* close data file */
exit(0); /* halt dma and exit */
}
/*--------------------------------------------------------------------------*/
reset_irq() /* clear interrupt request in hardware */
{
inp(0x3A0);
}